Skip to content

docs: CLAUDE.md becomes a pointer to AGENTS.md — the inlined never-miss digest leaves - #17168

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-17162-claude-md-pointer
Sep 9, 2026
Merged

docs: CLAUDE.md becomes a pointer to AGENTS.md — the inlined never-miss digest leaves#17168
os-zhuang merged 2 commits into
mainfrom
claude/issue-17162-claude-md-pointer

Conversation

@os-justin

@os-justin os-justin commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17162

Program anchor: #17161 (that card stays open — this repo is one of several).

Maintainer direct instruction, 2026-09-09, verbatim and untranslated:

「首先要明确 hotcrm 是元数据应用,平台功能应该在 objectstack中开发。 其次 claude.md 是不是直接让他阅读 agents.md 即可,没必要维护两套。所有仓库都有类似的问题」

What landed

CLAUDE.md goes 41 lines to 5: a title and one bold sentence naming AGENTS.md as the single
source of truth to read before the first edit. The four inlined never-miss rules leave the file.
.claude/hooks/** is untouched — the hooks keep enforcing worktree-first, the stash ban and the
process-kill ban exactly as before.

Each of the four was verified to survive in AGENTS.md by its own sentence, not by a word
hit, before deletion:

rule leaving CLAUDE.md anchoring sentence in AGENTS.md
claim-first AGENTS.md:393"Claim the issue BEFORE you write any code." Every agent here shares one GitHub identity, so the assignee field is only a presence bit; the identity record is the Claim: comment… (runs to :412, and carries the dispatch/seat split and the ⛔ never-reassign clause the digest compressed)
worktree-first AGENTS.md:217 — Prime Directive 11. "Worktree-first — never edit on the shared main checkout." …Before your first file edit, be in a dedicated worktree on a feature branch… (names both guard hooks and OS_ALLOW_MAIN_EDITS=1)
the git stash ban AGENTS.md:318"⛔ git stash is the sharpest thing the worktree does NOT isolate — never run a bare git stash push/pop." (names guard-shared-stash.sh, OS_ALLOW_STASH=1, and the collision-free replacements)
the kill-by-name ban AGENTS.md:528"⛔ One process table per container: kill only a PID you recorded, never a name" (guard-process-kill.sh)
content/docs/releases/ AGENTS.md:679 — Documentation Guardrails row, content/docs/releases/ · RELEASE-OWNED · "❌ Never edit in a code PR." Release notes are written centrally at release time… your PR's input is its changeset

Nothing was moved into AGENTS.md: all five sentences were already there, each strictly longer
than the digest's version of it. AGENTS.md is not in this diff.

The card's premise for the gate change is FALSIFIED — measured, not argued

The card and its dispatch both state that scripts/pm/check-governed-prose.mjs "counts
CLAUDE.md as one of two instruction surfaces that must name all five governed surfaces", so a
pointer file would red it and the gate must adapt. It does not read CLAUDE.md at all.

Its PROSE_SURFACES holds AGENTS.md (Prime Directive #14) and
.claude/skills/pm-dispatch/SKILL.md. CLAUDE.md appears in that gate only as a register
entry
— one of the five surfaces those two files must NAME. The gate's own verdict line says
so, and it is byte-identical before and after this PR:

✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.

Three ablation legs on the committed tree, each self-restoring, each with its on-disk landing
proven before the gate ran and git diff HEAD proven empty after:

leg mutation (on-disk proof) gate exit reading
B — positive control drop `skills/**` from the AGENTS.md PD#14 region (occurrences 2 → 1) 1 the gate is live in this tree and CAN red: "AGENTS.md:255-289 … does not name skills/** — the register governs it and this prose under-claims"
A1 — the predicted red CLAUDE.md replaced by prose with no pointer (occurrences of the string AGENTS.md in it: 0) 0 the ruling predicted red. It is green.
A2 — the decisive one CLAUDE.md deleted entirely 0 the gate treats an unreadable input as "cannot read — Red, not a skip (#4690)", so a green run with the file absent proves it never opens CLAUDE.md

So the ruled adaptation has nothing to attach to, and the ruled self-test pair cannot be
written: "pointer form ⇒ green" and "prose without a pointer ⇒ red" are the same run of a gate
that reads neither file.

Worse, taking the ruling literally would BREAK a gate. scripts/pm/dispatch-gates.mjs pins the
separation, with a comment saying exactly what to do when a premise like this one is wrong:

t('a one-root declaration does not reach the other root file', !proseHints.some((h) => hintCovers(h, 'CLAUDE.md')));

Read from the real gates, not fixtures: what is pinned is that the tree still HAS the
declarations. If one of these gates stops reading its root file, delete its case with the
declaration — never keep a case green by re-pointing it at a gate that never read the file.

scripts/pm/check-governed-prose.mjs is not in this diff. The ruling's intent holds
untouched and unweakened: the five-surface enumeration stays enforced on the two files that
actually carry it, and CLAUDE.md stays a registered governed surface both of them must name.

The one gate that DID need this PR: the line ratchet

scripts/pm/check-skill-line-ratchet.mjs pins CLAUDE.md at a ceiling of 41 with headroom 0.
Shrinking the file to 5 leaves 36 lines of slack. The gate is green either way — its slack nudge
only fires past 120 lines — but 36 lines of budget on the one file that existed to inline
must-never-miss rules is precisely what a future digest re-accretes into. Lowered 41 → 5, the
ratchet's own convention ("a ceiling may be LOWERED by any PR that shrinks its file — lowering is
always legitimate and encouraged", headroom 0 on every entry). This is a LOWERING and needs no
ruling of its own; the maintainer ruling is quoted in the entry because it is what emptied the
file.

✓ check-skill-line-ratchet: CLAUDE.md is 41 lines (ceiling 41; headroom 0).     ← before
✓ check-skill-line-ratchet: CLAUDE.md is 5 lines (ceiling 5; headroom 0).       ← after

Every other reader of CLAUDE.md, measured

grep -rn CLAUDE.md scripts .github .claude — no gate, hook or workflow requires prose in it:

  • scripts/pm/check-governed-merges.mjs — the governed register. Keeps listing CLAUDE.md, as required.
  • scripts/check-required-contexts.mjsCLAUDE.md is in the derivation population but not in INSTRUCTION_SURFACES; it enters that set "by naming [a registered context], never by sitting next to one that does", and it names none, before or after.
  • scripts/check-agent-test-spelling.mjs — corpus membership only (LOOSE_FILE_NAMES); scans for test-command spellings, of which the new file has none.
  • scripts/ci/select-gate-families.sh — maps the path to the agent-config family; unaffected.
  • .claude/hooks/guard-main-checkout-bash.sh, check-governed-queue-guard.mjs, check-doc-frontmatter.mjs — comments and self-test fixtures only.

Governed terminal

$ node scripts/pm/check-governed-merges.mjs --test CLAUDE.md scripts/pm/check-skill-line-ratchet.mjs
governed-surface predicate: 1 of 2 path(s) hit the register (5 surfaces, repo-agnostic).
  ⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
      No seat flips it ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14).
      One hit governs the whole PR — 「混合 diff 一条命中即整 PR 分叉」; proportion is not a question.
      CLAUDE.md ×1 — the repo-root Claude instruction file
  paths not on the register: scripts/pm/check-skill-line-ratchet.mjs
exit=3

Draft PR, maintainer merge. skip-changeset: nothing published moves — CLAUDE.md is a
repo-root instruction file and scripts/pm/** is repo tooling; neither is in any package's
files[].

维护者速读(草稿)

改了什么 — 根 CLAUDE.md 从 41 行缩到 5 行,只剩一句话:AGENTS.md 是唯一真相源,动手前先读它。
原先内联的四条「绝不能漏」规则(认领优先、worktree-first、禁 git stash / 禁按名杀进程、代码 PR 不
改 release notes)全部离开该文件——它们本来就一字不落地住在 AGENTS.md,本 PR 逐条核验过。
.claude/hooks/** 一行未动,三个守卫照常拦截。附带把行数棘轮里 CLAUDE.md 的上限从 41 降到 5。

为什么改 — 维护者裁决:「claude.md 是不是直接让他阅读 agents.md 即可,没必要维护两套」。两套指令
集的代价是漂移:摘要版会落后于 AGENTS.md,而只读 CLAUDE.md 的 agent 拿到的就是那份落后的摘要。
指针不会落后。

风险与代价(含回滚) — 已知代价(程序卡 #17161 已记录一次):Claude Code 会话启动自动加载
CLAUDE.md,AGENTS.md 只能经指针到达,所以跳过指针的会话在第一个 hook 触发前手里没有规则。托底的
是 hooks——worktree、stash、杀进程三条最贵的规则由 PreToolUse 守卫机械强制,不依赖任何一个 agent 读没
读文件。回滚成本为零:两个文件、一次 revert,无生成物、无 changeset、无发布面。

席位意见 — (留空,待评审席位定稿)

你要做的 — 只需确认两件事,然后人工合并这个 draft PR:① 那句指针的措辞是否是你要的口吻;②
本 PR 没有按裁决第 4 条去改 check-governed-prose.mjs——因为实测证明那条裁决的前提不成立(该门禁根本
不读 CLAUDE.md,删掉整个文件它照样绿),而照裁决字面去改会让 check:pm-dispatch-gates 变红。证据在
上面的消融表里。如果你要的是另一种处置,说一声。

Acceptance notes

Gates — all green at 41329812, the final commit.

✓ dispatch-gates --ran: 33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED.
✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces … and claim no others.   (identical before and after)
✓ check-skill-line-ratchet: CLAUDE.md is 5 lines (ceiling 5; headroom 0).
✓ dispatch-gates self-test: 1624 cases pass.        (pnpm check:pm-dispatch-gates, exit 0)
$ pnpm lint            # eslint . --no-inline-config, repo-wide, exit 0

Re-derived on the actual diff with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at
merge commit 41329812: 33 commands, all run, all exit 0. The dispatch's 34-command lead
included pnpm check:pm-governed-prose; it drops out because the re-derivation sees the real
diff, which does not touch that gate. Ran it anyway, before and after — green both times, same
verdict line. pnpm check:pm-dispatch-gates was run detached with its exit captured to a file
and waited on in the foreground, per the dispatch.

Shallow checkout. git rev-parse --is-shallow-repository is true here. No ancestry verdict
in this PR rests on a negative merge-base --is-ancestor; the AGENTS.md survival checks are
content greps on the working tree at origin/main, which a shallow checkout answers exactly.

origin/main merged in. f801e7d7 (#17142) landed mid-run and touched
scripts/pm/dispatch-gates.mjs, the derivation tool itself, which the tool reported as a STALE
TREE warning. Merged and re-derived on the current tool: the 33-command list is byte-identical to
the pre-merge one.

Out of scope, noted, not filed. Six script/agent comments cite CLAUDE.md as the place a
rule is statedscripts/check-vendor-version-stamps.mjs:226, scripts/check-docs-single-h1.mjs:109
and :117, scripts/import-prerequisite.mjs:12 and :382, scripts/pm/ci-failure.mjs:215,
.claude/agents/os-dev.md:35, .claude/workflows/docs-accuracy-audit.js:41. After this PR those
rules are stated in AGENTS.md instead. Not filed and not fixed here: CLAUDE.md still exists
and points at AGENTS.md, so every one of those citations still routes a reader to the rule —
one hop longer, never a dead end. Who picks this up: the sibling sub-issues of #17161 do the same
rewrite in objectui and cloud, and whoever lands the last of them is the natural place to
re-point the citations across all repos in one pass. No gate reads them.


Generated by Claude Code


Generated by Claude Code

…ss digest leaves

CLAUDE.md restated four never-miss rules (claim-first, worktree-first, the stash /
name-matched-kill ban, and the releases/ ban) with their hooks — a second instruction
set to keep in sync with AGENTS.md, which already carries every one of them. Each of
the four was verified to survive in AGENTS.md by its own sentence before deletion, and
the hooks in .claude/hooks/ are untouched: they keep enforcing what they enforce.

The line ratchet's CLAUDE.md ceiling is lowered 41 -> 5 in the same commit. The gate is
green either way (slack only nudges past 120 lines), but 36 lines of headroom on a file
that exists to inline must-never-miss rules is exactly the budget a future digest would
re-accrete into — the growth that entry exists to price.

Claude-Session: https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

维护者速读 — PR #17168(#17162,skills 席终稿,2026-09-09T14:28Z)

改了什么 — objectstack 根 CLAUDE.md 从 41 行缩到 5 行:标题加一句「AGENTS.md 是唯一事实源,第一次改文件前通读;一套指令只维护一处,这里不复述任何规则、钩子清单或守卫,以后也不加」。原先内联的四条「绝不能漏」规则(认领优先、worktree-first、禁 git stash / 禁按名杀进程、代码 PR 不改 release notes)全部离开该文件——五句原话都在 AGENTS.md(:217 / :318 / :393 / :528 / :679),本席逐句核过。.claude/hooks/** 一字未动。附带把行数棘轮里 CLAUDE.md 的上限从 41 降到 5(降上限不需裁决,注释里引了你的原话)。这是你「claude.md 直接让他阅读 agents.md 即可,没必要维护两套」在 objectstack 的落地,程序父单 #17161

为什么改 — 两套指令集的代价是漂移:摘要落后于原文,而只读 CLAUDE.md 的 agent 看不出它过期;指针没有可过期的东西。

风险与代价(含回滚) — 已知代价:Claude Code 会话自动加载的是 CLAUDE.md,AGENTS.md 经指针到达;托底是三个 PreToolUse 钩子原样在。回滚 = revert 两文件一提交。

席位意见 — 接受。本席在分支头复核:五句原话在;check-governed-prose 前后逐字节相同的绿(本席派发词里让 dev 改这个门禁那一条是本席读错了——该门禁根本不读 CLAUDE.md,dev 用三条消融证明后拒绝执行,是对的,本席已在 ACCEPT 里认下);棘轮本席重跑绿(CLAUDE.md is 5 lines (ceiling 5; headroom 0));受管判据 exit 3;merge-tree 干净;CI 19 绿 / 10 跳过 / 1 在跑(14:27Z,Lint & Repo Gates)。dev 顺手发现的八处「规则见 CLAUDE.md」注释引用(多一跳、不断链、无门禁读它)已记到 #17161,由程序最后一个子单一次性改指向。

你要做的 — 看一眼那五行的口吻;CI 全绿后人工合并,或由 os-zhuang / hotlong 批准后本席入队(受管面,席位不翻 ready、不入队、不挂 auto-merge)。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation needs-user-decision size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLAUDE.md becomes a pointer to AGENTS.md — the inlined never-miss digest leaves; check-governed-prose reads the pointer form

3 participants